
package de.uni.wuppertal.server.http;

import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import java.net.URI;
import java.net.URLConnection;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.Scanner;
import java.util.TimeZone;

import de.uni.wuppertal.server.ErrorLogEntry;
import de.uni.wuppertal.server.InformationLogEntry;
import de.uni.wuppertal.server.InputLogEntry;
import de.uni.wuppertal.server.WarningLogEntry;
import de.uni.wuppertal.server.ui.MainWindow;
import de.uni.wuppertal.server.util.Resource;

/**
 * @author Martin Schröder
 * @author Patrik Nordsiek
 */
public class HTTPServerThread implements Runnable
{
	private HTTPServer server;
	private Socket socket;
	private BufferedReader reader;
	private OutputStream outputStream;
	private HTTPRequest request;
	private HTTPResponse response;
	
	private static DateFormat dateFormat;
	
	static
	{
		dateFormat = new SimpleDateFormat("E, d MMM yyyy HH:mm:ss", new Locale("en"));
		dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
	}
	
	public HTTPServerThread(HTTPServer server, Socket socket)
	{
		try
		{
			this.server = server;
			this.socket = socket;
			
			reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
			outputStream = socket.getOutputStream();
		}
		catch(Throwable e)
		{
			throw new HTTPServerException(e.getMessage());
		}
	}
	
	protected void disableBrowserCaching()
	{
		response.setHeader("Expires", "Sat, 26 Jul 1997 05:00:00 GMT");
		response.setHeader("Last-Modified", dateFormat.format(new Date()) + " GMT");
		response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate, proxy-revalidate");
		response.setHeader("Cache-Control", "pre-check=0, post-check=0, max-age=0", false);
		response.setHeader("Pragma", "no-cache");
	}
	
	@Override
	public void run()
	{
		MainWindow window = MainWindow.getInstance();
		
		try
		{
			String line;
			
			request = new HTTPRequest(server);
			response = new HTTPResponse(outputStream);
			
			response.setHeader("Date", dateFormat.format(new Date()) + " GMT");
			response.setHeader("Connection", "close");
			
			// Read Headers:
			
			line = reader.readLine();
			
			Scanner scanner = new Scanner(line);
			
			request.setMethod(scanner.next().toUpperCase());
			request.setURI(URI.create(scanner.next()));
			request.setProtocol(scanner.next());
			
			while(true)
			{
				line = reader.readLine();
				
				if(line == null || line.equals(""))
				{
					break;
				}
				
				String hname, hvalue;
				
				Scanner hscanner = new Scanner(line);
				hscanner.useDelimiter(":");
				
				hname = hscanner.next().toLowerCase();
				hvalue = "";
				
				boolean appendColon = false;
				
				while(hscanner.hasNext())
				{
					if(appendColon)
					{
						hvalue += ":";
					}
					else
					{
						appendColon = true;
					}
					
					hvalue += hscanner.next();
				}
				
				hvalue = hvalue.trim();
				
				request.setHeader(hname, hvalue);
			}
			
			window.log(new InputLogEntry("HTTP - " + request.getMethod() + " " + request.getResource()));
			
			String filepath = "htdocs" + request.getResource();
			
			filepath = server.getRootDirectory() + request.getResource();
			
			File file = new File(filepath).getAbsoluteFile();
			File rootDirectory = new File(server.getRootDirectory());
			
			if(!file.getAbsolutePath().startsWith(rootDirectory.getAbsolutePath()))
			{
				throw new HTTPServerException("Access Denied");
			}
			
			if(file.canRead())
			{
				if(file.isDirectory())
				{
					window.log(new InformationLogEntry("Accessing Directory: " + file.getAbsolutePath()));
					
					File root = new File(file.getAbsolutePath() + "/index.html");
					
					if(root.canRead())
					{
						file = root.getAbsoluteFile();
					}
					else
					{
						String title = "Index of " + HTTPServer.htmlEntities(request.getResource());
						StringBuffer buffer = new StringBuffer();
						
						buffer.append("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">");
						buffer.append("<html dir=\"ltr\" xmlns=\"http://www.w3.org/1999/xhtml\">");
						buffer.append("<head><title>");
						buffer.append(title);
						buffer.append("</title><meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\" /></head>");
						buffer.append("<body><h1>");
						buffer.append(title);
						buffer.append("</h1><ul>");
						
						if(request.getResource().split("/").length > 1)
						{
							buffer.append("<li><a href=\"..\">..</a></li>");
						}
						
						for(String f : file.list())
						{
							String sub = HTTPServer.htmlEntities(f);
							
							if(new File(file.getAbsolutePath() + "/" + f).isDirectory())
							{
								sub += "/";
							}
							
							buffer.append("<li><a href=\"");
							buffer.append(sub);
							buffer.append("\">");
							buffer.append(sub);
							buffer.append("</a></li>");
						}
						
						buffer.append("</ul></body></html>");
						
						response.setHeader("Content-Type", "text/html; charset=\"utf-8\"");
						
						disableBrowserCaching();
						
						response.setBody(buffer.toString().getBytes("UTF-8"));
					}
				}
				
				if(file.isFile())
				{
					String lastModified = dateFormat.format(new Date(file.lastModified())) + " GMT";
					String etag = HTTPServer.generateFileETag(file);
					String requestLastModified = (request.hasHeader("If-Modified-Since")) ? request.getHeader("If-Modified-Since").getFirst() : null;
					String requestETag = (request.hasHeader("If-None-Match")) ? request.getHeader("If-None-Match").getFirst() : null;
					
					if(requestETag != null && etag != null && lastModified != null && requestLastModified != null)
					{
						if(requestETag.equals(etag) && requestLastModified.equals(lastModified))
						{
							window.log(new InformationLogEntry("File valid in browser cache: " + file.getAbsolutePath()));
							
							response.setCode(HTTPResponse.CODE_NOT_MODIFIED);
							
							response.setHeader("Last-Modified", lastModified);
							response.setHeader("ETag", etag);
							
							return;
						}
					}
					
					window.log(new InformationLogEntry("Accessing File: " + file.getAbsolutePath()));
					
					response.setHeader("Last-Modified", lastModified);
					
					String mimetype = URLConnection.guessContentTypeFromName(file.getAbsolutePath());
					
					if(mimetype == null || mimetype.equals(""))
					{
						mimetype = "application/octet-stream";
					}
					
					response.setHeader("Content-Type", mimetype);
					response.setHeader("Cache-Control", "must-revalidate, proxy-revalidate, private, max-age=0");
					response.setHeader("Last-Modified", lastModified);
					response.setHeader("ETag", etag);
					
					response.setBody(file);
				}
			}
			else
			{
				String checker = request.getResource().toLowerCase();
				
				response.setCode(HTTPResponse.CODE_NOT_FOUND);
				
				if(checker.equals("/favicon.ico") || checker.equals("/robots.txt"))
				{
					return;
				}
				
				window.log(new WarningLogEntry("Resource not found: " + file.getAbsolutePath()));
				
				response.setHeader("Content-Type", "text/html; charset=\"utf-8\"");
				
				disableBrowserCaching();
				
				String body = Resource.getText("http/error-404.html", "UTF-8");
				
				body = body.replaceAll("###MESSAGE###", HTTPServer.htmlEntities("Resource not found: " + file.getAbsolutePath()));
				
				response.setBody(body.getBytes("UTF-8"));
			}
		}
		catch(Throwable e)
		{
			window.log(new ErrorLogEntry(e));
			
			e.printStackTrace();
			
			try
			{
				response.setCode(HTTPResponse.CODE_INTERNAL_SERVER_ERROR);
				
				response.setHeader("Content-Type", "text/html; charset=\"utf-8\"");
				
				disableBrowserCaching();
				
				String body = Resource.getText("http/error-500.html", "UTF-8");
				
				body = body.replaceAll("###MESSAGE###", HTTPServer.htmlEntities(e.getMessage()));
				
				response.setBody(body.getBytes("UTF-8"));
			}
			catch(Throwable ex)
			{
				window.log(new ErrorLogEntry(ex));
				
				ex.printStackTrace();
			}
		}
		finally
		{
			try
			{
				response.send();
				
				socket.close();
			}
			catch(Throwable e)
			{
			}
		}
	}
}
